    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_DIR_NAV_KERNEl_1_
    #define DLIB_DIR_NAV_KERNEl_1_

    #ifdef DLIB_ISO_CPP_ONLY
    #error "DLIB_ISO_CPP_ONLY is defined so you can't use this OS dependent code.  Turn DLIB_ISO_CPP_ONLY off if you want to use it."
    #endif

    #include "../platform.h"

    #include "dir_nav_kernel_abstract.h"
    #include <string>
    #include "../uintn.h"
    #include "../algs.h"

    #include "../windows_magic.h"
    #include <windows.h>
    #include <vector>
    #include "../stl_checked.h"
    #include "../enable_if.h"
    #include "../queue.h"
    #include <chrono>

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // file object
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class file
        {
            /*!
                INITIAL VALUES
                    state.name        == name()
                    state.full_name   == full_name()
                    state.file_size   == size()
                    state.last_modified == last_modified()

                CONVENTION
                    state.name        == name()
                    state.full_name   == full_name()
                    state.file_size   == size()
                    state.last_modified == last_modified()

            !*/

            friend class directory;

            struct data
            {
                uint64 file_size;
                std::string name;
                std::string full_name;
                std::chrono::time_point<std::chrono::system_clock> last_modified;
            };

            void init ( const std::string& name);

        public:

            struct private_constructor{};
            inline file (
                const std::string& name,
                const std::string& full_name,
                const uint64 file_size,
                const std::chrono::time_point<std::chrono::system_clock>& last_modified,
                private_constructor
            )
            {
                state.file_size = file_size;
                state.name = name;
                state.full_name = full_name;
                state.last_modified = last_modified;
            }

            class file_not_found : public error {
                public: file_not_found(const std::string& s): error(s){}
            };

            inline file (
            )
            {
                state.file_size = 0;
            }

            file (
                const std::string& name
            ) { init(name); }

            file (
                const char* name
            ) { init(name); }

            inline const std::string& name (
            ) const { return state.name; }

            inline  const std::string& full_name (
            ) const { return state.full_name; }

            operator std::string (
            ) const { return full_name(); }

            inline uint64 size (
            ) const { return state.file_size; }

            inline std::chrono::time_point<std::chrono::system_clock> last_modified (
            ) const { return state.last_modified; }

            bool operator == (
                const file& rhs
            ) const;

            bool operator != (
                const file& rhs
            ) const { return !(*this == rhs); }

            inline bool operator < (
                const file& item
            ) const { return full_name() < item.full_name(); }

            inline void swap (
                file& item
            )
            {
                exchange(state,item.state);
            }

        private:

            data state;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // directory object
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class directory
        {
            /*!
                INITIAL VALUES
                    state.name        == name()
                    state.full_name   == full_name()

                CONVENTION
                    state.name        == name()
                    state.full_name   == full_name()
                    is_root()          == state.name.size() == 0

            !*/

            void init (const std::string& name);

        public:

            struct data
            {
                std::string name;
                std::string full_name;
            };

            /*
                The reason we don't just make this constructor actually
                private is because doing it this way avoids a bug that
                sometimes occurs in visual studio 7.1.  The bug has
                something to do with templated friend functions
                such as the get_filesystem_roots() function below if
                it was declared as a friend template of this class.
            */
            struct private_constructor{};
            inline directory (
                const std::string& name,
                const std::string& full_name,
                private_constructor
            )
            {
                state.name = name;
                state.full_name = full_name;
            }

            class dir_not_found : public error {
                public: dir_not_found(const std::string& s):error(s){}
            };
            class listing_error : public error {
                public: listing_error(const std::string& s):error(s){}
            };

            inline directory (
            )
            {
            }

            directory (
                const std::string& name
            ) { init(name); }

            directory (
                const char* name
            ) { init(name); }

            static char get_separator (
            );

            template <
                typename queue_of_files
                >
            void get_files (
                queue_of_files& files
            ) const;

            template <
                typename queue_of_dirs
                >
            void get_dirs (
                queue_of_dirs& dirs
            ) const;

            std::vector<file> get_files (
            ) const
            {
                std::vector<file> temp_vector;
                get_files(temp_vector);
                return temp_vector;
            }

            std::vector<directory> get_dirs (
            ) const
            {
                std::vector<directory> temp_vector;
                get_dirs(temp_vector);
                return temp_vector;
            }

            const directory get_parent (
            ) const;

            inline bool is_root (
            ) const { return state.name.size() == 0; }

            inline const std::string& name (
            ) const { return state.name; }

            inline const std::string& full_name (
            ) const { return state.full_name; }

            operator std::string (
            ) const { return full_name(); }

            bool operator == (
                const directory& rhs
            ) const;

            bool operator != (
                const directory& rhs
            ) const { return !(*this == rhs); }

            inline bool operator < (
                const directory& item
            ) const { return full_name() < item.full_name(); }

            inline void swap (
                directory& item
            )
            {
                exchange(state,item.state);
            }

        private:

            // member data
            data state;

            bool is_root_path (
                const std::string& path
            ) const;
            /*!
                ensures
                    - returns true if path is a root path.
                      Note that this function considers root paths that don't
                      have a trailing separator to also be valid.
            !*/

        };

    // ----------------------------------------------------------------------------------------

        inline std::ostream& operator<< (
            std::ostream& out,
            const directory& item
        ) { out << (std::string)item; return out; }

        inline std::ostream& operator<< (
            std::ostream& out,
            const file& item
        ) { out << (std::string)item; return out; }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dir
            >
        typename disable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots (
            queue_of_dir& roots
        )
        {
            roots.clear();
            const DWORD mask = GetLogicalDrives();
            DWORD bit = 1;
            char buf[] = "A:\\";

            do
            {
                if (mask & bit)
                {
                    directory dir("",buf,directory::private_constructor());
                    roots.enqueue(dir);
                }
                bit <<= 1;
                ++buf[0];
            } while (buf[0] != 'Z');
        }

        template <
            typename queue_of_dir
            >
        typename enable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots (
            queue_of_dir& roots
        )
        {
            roots.clear();
            const DWORD mask = GetLogicalDrives();
            DWORD bit = 1;
            char buf[] = "A:\\";

            do
            {
                if (mask & bit)
                {
                    directory dir("",buf,directory::private_constructor());
                    roots.push_back(dir);
                }
                bit <<= 1;
                ++buf[0];
            } while (buf[0] != 'Z');
        }

    // ----------------------------------------------------------------------------------------

        inline void swap (
            file& a,
            file& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------

        inline void swap (
            directory& a,
            directory& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // templated member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        typename disable_if<is_std_vector<queue_of_files>,void>::type
        directory_helper_get_files (
            const directory::data& state,
            queue_of_files& files
        )
        {
            typedef directory::listing_error listing_error;
            typedef file::private_constructor private_constructor;

            files.clear();
            if (state.full_name.size() == 0)
                throw listing_error("This directory object currently doesn't represent any directory.");

            HANDLE ffind = INVALID_HANDLE_VALUE;
            try
            {
                WIN32_FIND_DATAA data;
                std::string path = state.full_name;
                // ensure that the path ends with a separator
                if (path[path.size()-1] != directory::get_separator())
                    path += directory::get_separator();

                ffind = FindFirstFileA((path+"*").c_str(), &data);
                if (ffind == INVALID_HANDLE_VALUE)
                {
                    throw listing_error("Unable to list the contents of " + state.full_name);
                }

                bool no_more_files = false;
                do
                {
                    if ((data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) == 0)
                    {
                        uint64 file_size = data.nFileSizeHigh;
                        file_size <<= 32;
                        file_size |= data.nFileSizeLow;

                        ULARGE_INTEGER ull;
                        ull.LowPart = data.ftLastWriteTime.dwLowDateTime;
                        ull.HighPart = data.ftLastWriteTime.dwHighDateTime;
                        std::chrono::nanoseconds epoch(100 * (ull.QuadPart - 116444736000000000));
                        auto last_modified = std::chrono::time_point<std::chrono::system_clock>(std::chrono::duration_cast<std::chrono::system_clock::duration>(epoch));

                        // this is a file so add it to the queue
                        file temp(data.cFileName,path+data.cFileName,file_size, last_modified, private_constructor());
                        files.enqueue(temp);
                    }

                    if (FindNextFileA(ffind,&data) == 0)
                    {
                        // an error occurred
                        if ( GetLastError() == ERROR_NO_MORE_FILES)
                        {
                            // there are no more files
                            no_more_files = true;
                        }
                        else
                        {
                            // there was an error
                            throw listing_error("Unable to list the contents of " + state.full_name);
                        }
                    }
                } while (no_more_files == false);

                FindClose(ffind);
                ffind = INVALID_HANDLE_VALUE;
            }
            catch (...)
            {
                if (ffind != INVALID_HANDLE_VALUE)
                    FindClose(ffind);
                files.clear();
                throw;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        typename enable_if<is_std_vector<queue_of_files>,void>::type
        directory_helper_get_files (
            const directory::data& state,
            queue_of_files& files
        )
        {
            queue<file>::kernel_2a temp_files;
            directory_helper_get_files(state,temp_files);

            files.clear();

            // copy the queue of files into the vector
            temp_files.reset();
            while (temp_files.move_next())
            {
                files.push_back(temp_files.element());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_files
            >
        void directory::
        get_files (
            queue_of_files& files
        ) const
        {
            // the reason for this indirection here is because it avoids a bug in
            // the mingw version of gcc
            directory_helper_get_files(state,files);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        typename disable_if<is_std_vector<queue_of_dirs>,void>::type
        directory_helper_get_dirs (
            const directory::data& state,
            queue_of_dirs& dirs
        )
        {
            typedef directory::listing_error listing_error;
            typedef directory::private_constructor private_constructor;

            dirs.clear();
            if (state.full_name.size() == 0)
                throw listing_error("This directory object currently doesn't represent any directory.");

            HANDLE dfind = INVALID_HANDLE_VALUE;
            try
            {
                WIN32_FIND_DATAA data;
                std::string path = state.full_name;
                // ensure that the path ends with a separator
                if (path[path.size()-1] != directory::get_separator())
                    path += directory::get_separator();

                dfind = FindFirstFileA((path+"*").c_str(), &data);
                if (dfind == INVALID_HANDLE_VALUE)
                {
                    throw listing_error("Unable to list the contents of " + state.full_name);
                }

                bool no_more_files = false;
                do
                {
                    std::string tname(data.cFileName);
                    if ((data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) != 0 &&
                        tname != "." &&
                        tname != "..")
                    {
                        // this is a directory so add it to the queue
                        directory temp(tname,path+tname,private_constructor());
                        dirs.enqueue(temp);
                    }

                    if (FindNextFileA(dfind,&data) == 0)
                    {
                        // an error occurred
                        if ( GetLastError() == ERROR_NO_MORE_FILES)
                        {
                            // there are no more files
                            no_more_files = true;
                        }
                        else
                        {
                            // there was an error
                            throw listing_error("Unable to list the contents of " + state.full_name);
                        }
                    }
                } while (no_more_files == false);

                FindClose(dfind);
                dfind = INVALID_HANDLE_VALUE;
            }
            catch (...)
            {
                if (dfind != INVALID_HANDLE_VALUE)
                    FindClose(dfind);
                dirs.clear();
                throw;
            }

        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        typename enable_if<is_std_vector<queue_of_dirs>,void>::type
        directory_helper_get_dirs (
            const directory::data& state,
            queue_of_dirs& dirs
        )
        {
            queue<directory>::kernel_2a temp_dirs;
            directory_helper_get_dirs(state,temp_dirs);

            dirs.clear();

            // copy the queue of dirs into the vector
            temp_dirs.reset();
            while (temp_dirs.move_next())
            {
                dirs.push_back(temp_dirs.element());
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename queue_of_dirs
            >
        void directory::
        get_dirs (
            queue_of_dirs& dirs
        ) const
        {
            // the reason for this indirection here is because it avoids a bug in
            // the mingw version of gcc
            directory_helper_get_dirs(state,dirs);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

    }

    #ifdef NO_MAKEFILE
    #include "dir_nav_kernel_1.cpp"
    #endif

    #endif // DLIB_DIR_NAV_KERNEl_1_